/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 Hex_27
 * Copyright (c) 2020 Crypto Morin
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package me.atbar.moonlib.control;

import com.google.common.base.Enums;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.apache.commons.lang.WordUtils;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * <b>XMaterial</b> - Data Values/Pre-flattening<br>
 * Supports 1.8-1.15<br>
 * 1.13 and above as priority.
 * <p>
 * This class is mainly designed to support ItemStacks.
 * If you want to use it on blocks you'll have to
 * use <a href="https://github.com/CryptoMorin/XSeries/blob/master/XBlock.java">XBlock</a>
 * <p>
 * Pre-flattening: https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening
 * Materials: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html
 * Materials (1.12): https://helpch.at/docs/1.12.2/index.html?org/bukkit/Material.html
 * Material IDs: https://minecraft-ids.grahamedgecombe.com/
 * Material Source Code: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Material.java
 * XMaterial v1: https://www.spigotmc.org/threads/329630/
 *
 * @author Crypto Morin
 * @version 4.0.0
 * @see Material
 * @see ItemStack
 */
public enum XMaterial {
    ACACIA_BOAT("BOAT_ACACIA"),
    ACACIA_BUTTON("WOOD_BUTTON"),
    ACACIA_DOOR("ACACIA_DOOR_ITEM"),
    ACACIA_FENCE,
    ACACIA_FENCE_GATE,
    ACACIA_LEAVES("LEAVES_2"),
    ACACIA_LOG("LOG_2"),
    ACACIA_PLANKS(4, "WOOD"),
    ACACIA_PRESSURE_PLATE("WOOD_PLATE"),
    ACACIA_SAPLING(4, "SAPLING"),
    ACACIA_SIGN("SIGN"),
    ACACIA_SLAB(4, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"),
    ACACIA_STAIRS,
    ACACIA_TRAPDOOR("TRAP_DOOR"),
    ACACIA_WALL_SIGN("SIGN_POST", "WALL_SIGN"),
    ACACIA_WOOD("LOG_2"),
    ACTIVATOR_RAIL,
    /**
     * https://minecraft.gamepedia.com/Air
     * {@link Material#isAir()}
     *
     * @see #VOID_AIR
     * @see #CAVE_AIR
     */
    AIR,
    ALLIUM(2, "RED_ROSE"),
    ANDESITE(5, "STONE"),
    ANDESITE_SLAB,
    ANDESITE_STAIRS,
    ANDESITE_WALL,
    ANVIL,
    APPLE,
    ARMOR_STAND,
    ARROW,
    ATTACHED_MELON_STEM(7, "MELON_STEM"),
    ATTACHED_PUMPKIN_STEM(7, "PUMPKIN_STEM"),
    AZURE_BLUET(3, "RED_ROSE"),
    BAKED_POTATO,
    BAMBOO("1.14", "SUGAR_CANE", ""),
    BAMBOO_SAPLING("1.14"),
    BARREL("1.14", "CHEST", ""),
    BARRIER,
    BAT_SPAWN_EGG(65, "MONSTER_EGG"),
    BEACON,
    BEDROCK,
    BEEF("RAW_BEEF"),
    BEEHIVE("1.15"),
    /**
     * Beetroot is a known material in pre-1.13
     * Use XBlock when comparing block types.
     */
    BEETROOT("BEETROOT_BLOCK"),
    BEETROOTS("BEETROOT"),
    BEETROOT_SEEDS,
    BEETROOT_SOUP,
    BEE_NEST("1.15"),
    BEE_SPAWN_EGG("1.15"),
    BELL("1.14"),
    BIRCH_BOAT("BOAT_BIRCH"),
    BIRCH_BUTTON("WOOD_BUTTON"),
    BIRCH_DOOR("BIRCH_DOOR_ITEM"),
    BIRCH_FENCE,
    BIRCH_FENCE_GATE,
    BIRCH_LEAVES(2, "LEAVES"),
    BIRCH_LOG(2, "LOG"),
    BIRCH_PLANKS(2, "WOOD"),
    BIRCH_PRESSURE_PLATE("WOOD_PLATE"),
    BIRCH_SAPLING(2, "SAPLING"),
    BIRCH_SIGN("SIGN"),
    BIRCH_SLAB(2, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"),
    BIRCH_STAIRS("BIRCH_WOOD_STAIRS"),
    BIRCH_TRAPDOOR("TRAP_DOOR"),
    BIRCH_WALL_SIGN("SIGN_POST", "WALL_SIGN"),
    BIRCH_WOOD(2, "LOG"),
    BLACK_BANNER("BANNER", "STANDING_BANNER"),
    BLACK_BED(15, "BED", "BED_BLOCK"),
    BLACK_CARPET(15, "CARPET"),
    BLACK_CONCRETE(15, "CONCRETE"),
    BLACK_CONCRETE_POWDER(15, "CONCRETE_POWDER"),
    BLACK_DYE("1.14", "INK_SACK"),
    BLACK_GLAZED_TERRACOTTA(15, "1.12", "HARD_CLAY", "STAINED_CLAY", "BLACK_TERRACOTTA"),
    BLACK_SHULKER_BOX,
    BLACK_STAINED_GLASS(15, "STAINED_GLASS"),
    BLACK_STAINED_GLASS_PANE(15, "STAINED_GLASS_PANE"),
    BLACK_TERRACOTTA(15, "HARD_CLAY", "STAINED_CLAY"),
    BLACK_WALL_BANNER("WALL_BANNER"),
    BLACK_WOOL(15, "WOOL"),
    BLAST_FURNACE("1.14", "FURNACE", ""),
    BLAZE_POWDER,
    BLAZE_ROD,
    BLAZE_SPAWN_EGG(61, "MONSTER_EGG"),
    BLUE_BANNER(11, "BANNER", "STANDING_BANNER"),
    BLUE_BED(4, "BED", "BED_BLOCK"),
    BLUE_CARPET(11, "CARPET"),
    BLUE_CONCRETE(11, "CONCRETE"),
    BLUE_CONCRETE_POWDER(11, "CONCRETE_POWDER"),
    BLUE_DYE(4, "INK_SACK", "LAPIS_LAZULI"),
    BLUE_GLAZED_TERRACOTTA(11, "1.12", "HARD_CLAY", "STAINED_CLAY", "BLUE_TERRACOTTA"),
    BLUE_ICE("1.13", "PACKED_ICE", ""),
    BLUE_ORCHID(1, "RED_ROSE"),
    BLUE_SHULKER_BOX,
    BLUE_STAINED_GLASS(11, "STAINED_GLASS"),
    BLUE_STAINED_GLASS_PANE(11, "THIN_GLASS", "STAINED_GLASS_PANE"),
    BLUE_TERRACOTTA(11, "STAINED_CLAY"),
    BLUE_WALL_BANNER(11, "WALL_BANNER"),
    BLUE_WOOL(11, "WOOL"),
    BONE,
    BONE_BLOCK,
    BONE_MEAL(15, "INK_SACK"),
    BOOK,
    BOOKSHELF,
    BOW,
    BOWL,
    BRAIN_CORAL("1.13"),
    BRAIN_CORAL_BLOCK("1.13"),
    BRAIN_CORAL_FAN("1.13"),
    BRAIN_CORAL_WALL_FAN,
    BREAD,
    BREWING_STAND("BREWING_STAND_ITEM"),
    BRICK("CLAY_BRICK"),
    BRICKS("BRICK"),
    BRICK_SLAB(4, "STEP"),
    BRICK_STAIRS,
    BRICK_WALL,
    BROWN_BANNER(3, "BANNER", "STANDING_BANNER"),
    BROWN_BED(12, "BED", "BED_BLOCK"),
    BROWN_CARPET(12, "CARPET"),
    BROWN_CONCRETE(12, "CONCRETE"),
    BROWN_CONCRETE_POWDER(12, "CONCRETE_POWDER"),
    BROWN_DYE(3, "INK_SACK", "COCOA", "COCOA_BEANS"),
    BROWN_GLAZED_TERRACOTTA(12, "1.12", "HARD_CLAY", "STAINED_CLAY", "BROWN_TERRACOTTA"),
    BROWN_MUSHROOM,
    BROWN_MUSHROOM_BLOCK("BROWN_MUSHROOM", "HUGE_MUSHROOM_1"),
    BROWN_SHULKER_BOX,
    BROWN_STAINED_GLASS(12, "STAINED_GLASS"),
    BROWN_STAINED_GLASS_PANE(12, "THIN_GLASS", "STAINED_GLASS_PANE"),
    BROWN_TERRACOTTA(12, "STAINED_CLAY"),
    BROWN_WALL_BANNER(3, "WALL_BANNER"),
    BROWN_WOOL(12, "WOOL"),
    BUBBLE_COLUMN("1.13"),
    BUBBLE_CORAL("1.13"),
    BUBBLE_CORAL_BLOCK("1.13"),
    BUBBLE_CORAL_FAN("1.13"),
    BUBBLE_CORAL_WALL_FAN,
    BUCKET,
    CACTUS,
    CAKE("CAKE_BLOCK"),
    CAMPFIRE("1.14"),
    CARROT("CARROT_ITEM"),
    CARROTS("CARROT"),
    CARROT_ON_A_STICK("CARROT_STICK"),
    CARTOGRAPHY_TABLE("1.14", "CRAFTING_TABLE", ""),
    CARVED_PUMPKIN(1, "1.13", "PUMPKIN", ""),
    CAT_SPAWN_EGG,
    CAULDRON("CAULDRON_ITEM"),
    /**
     * 1.13 tag is not added because it's the same thing as {@link #AIR}
     *
     * @see #VOID_AIR
     */
    CAVE_AIR("AIR"),
    CAVE_SPIDER_SPAWN_EGG(59, "MONSTER_EGG"),
    CHAINMAIL_BOOTS,
    CHAINMAIL_CHESTPLATE,
    CHAINMAIL_HELMET,
    CHAINMAIL_LEGGINGS,
    CHAIN_COMMAND_BLOCK("COMMAND", "COMMAND_CHAIN"),
    CHARCOAL(1, "COAL"),
    CHEST("LOCKED_CHEST"),
    CHEST_MINECART("STORAGE_MINECART"),
    CHICKEN("RAW_CHICKEN"),
    CHICKEN_SPAWN_EGG(93, "MONSTER_EGG"),
    CHIPPED_ANVIL(1, "ANVIL"),
    CHISELED_QUARTZ_BLOCK(1, "QUARTZ_BLOCK"),
    CHISELED_RED_SANDSTONE(1, "RED_SANDSTONE"),
    CHISELED_SANDSTONE(1, "SANDSTONE"),
    CHISELED_STONE_BRICKS(3, "SMOOTH_BRICK"),
    CHORUS_FLOWER("1.9"),
    CHORUS_FRUIT("1.9"),
    CHORUS_PLANT("1.9"),
    CLAY,
    CLAY_BALL,
    CLOCK("WATCH"),
    COAL,
    COAL_BLOCK,
    COAL_ORE,
    COARSE_DIRT(1, "DIRT"),
    COBBLESTONE,
    COBBLESTONE_SLAB(3, "STEP"),
    COBBLESTONE_STAIRS,
    COBBLESTONE_WALL("COBBLE_WALL"),
    COBWEB("WEB"),
    COCOA("1.15"),
    COCOA_BEANS(3, "INK_SACK", "COCOA"),
    COD("RAW_FISH"),
    COD_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""),
    COD_SPAWN_EGG("1.13", "MONSTER_EGG", ""),
    COMMAND_BLOCK("COMMAND"),
    COMMAND_BLOCK_MINECART("COMMAND_MINECART"),
    COMPARATOR("REDSTONE_COMPARATOR", "REDSTONE_COMPARATOR_ON", "REDSTONE_COMPARATOR_OFF"),
    COMPASS,
    COMPOSTER("1.14", "CAULDRON", ""),
    CONDUIT("1.13", "BEACON"),
    COOKED_BEEF,
    COOKED_CHICKEN,
    COOKED_COD("COOKED_FISH"),
    COOKED_MUTTON,
    COOKED_PORKCHOP("PORK", "GRILLED_PORK"),
    COOKED_RABBIT,
    COOKED_SALMON(1, "COOKED_FISH"),
    COOKIE,
    CORNFLOWER(4, "1.14", "BLUE_DYE", ""),
    COW_SPAWN_EGG(92, "MONSTER_EGG"),
    CRACKED_STONE_BRICKS(2, "SMOOTH_BRICK"),
    CRAFTING_TABLE("WORKBENCH"),
    CREEPER_BANNER_PATTERN,
    CREEPER_HEAD(4, "SKULL", "SKULL_ITEM"),
    CREEPER_SPAWN_EGG(50, "MONSTER_EGG"),
    CREEPER_WALL_HEAD(4, "SKULL", "SKULL_ITEM"),
    CROSSBOW,
    CUT_RED_SANDSTONE("1.13"),
    CUT_RED_SANDSTONE_SLAB("STONE_SLAB2"),
    CUT_SANDSTONE("1.13"),
    CUT_SANDSTONE_SLAB("STEP"),
    CYAN_BANNER(6, "BANNER", "STANDING_BANNER"),
    CYAN_BED(9, "BED", "BED_BLOCK"),
    CYAN_CARPET(9, "CARPET"),
    CYAN_CONCRETE(9, "CONCRETE"),
    CYAN_CONCRETE_POWDER(9, "CONCRETE_POWDER"),
    CYAN_DYE(6, "INK_SACK"),
    CYAN_GLAZED_TERRACOTTA(9, "1.12", "HARD_CLAY", "STAINED_CLAY", "CYAN_TERRACOTTA"),
    CYAN_SHULKER_BOX,
    CYAN_STAINED_GLASS(9, "STAINED_GLASS"),
    CYAN_STAINED_GLASS_PANE(9, "STAINED_GLASS_PANE"),
    CYAN_TERRACOTTA(9, "HARD_CLAY", "STAINED_CLAY"),
    CYAN_WALL_BANNER(6, "WALL_BANNER"),
    CYAN_WOOL(9, "WOOL"),
    DAMAGED_ANVIL(2, "ANVIL"),
    DANDELION("YELLOW_FLOWER"),
    DARK_OAK_BOAT("BOAT_DARK_OAK"),
    DARK_OAK_BUTTON("WOOD_BUTTON"),
    DARK_OAK_DOOR("DARK_OAK_DOOR_ITEM"),
    DARK_OAK_FENCE,
    DARK_OAK_FENCE_GATE,
    DARK_OAK_LEAVES(1, "LEAVES", "LEAVES_2"),
    DARK_OAK_LOG(1, "LOG", "LOG_2"),
    DARK_OAK_PLANKS(5, "WOOD"),
    DARK_OAK_PRESSURE_PLATE("WOOD_PLATE"),
    DARK_OAK_SAPLING(5, "SAPLING"),
    DARK_OAK_SIGN("SIGN"),
    DARK_OAK_SLAB("WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"),
    DARK_OAK_STAIRS,
    DARK_OAK_TRAPDOOR("TRAP_DOOR"),
    DARK_OAK_WALL_SIGN("SIGN_POST", "WALL_SIGN"),
    DARK_OAK_WOOD(1, "LOG", "LOG_2"),
    DARK_PRISMARINE(1, "PRISMARINE"),
    DARK_PRISMARINE_SLAB("1.13"),
    DARK_PRISMARINE_STAIRS("1.13"),
    DAYLIGHT_DETECTOR("DAYLIGHT_DETECTOR_INVERTED"),
    DEAD_BRAIN_CORAL("1.13"),
    DEAD_BRAIN_CORAL_BLOCK("1.13"),
    DEAD_BRAIN_CORAL_FAN("1.13"),
    DEAD_BRAIN_CORAL_WALL_FAN("1.13"),
    DEAD_BUBBLE_CORAL("1.13"),
    DEAD_BUBBLE_CORAL_BLOCK("1.13"),
    DEAD_BUBBLE_CORAL_FAN("1.13"),
    DEAD_BUBBLE_CORAL_WALL_FAN("1.13"),
    DEAD_BUSH,
    DEAD_FIRE_CORAL("1.13"),
    DEAD_FIRE_CORAL_BLOCK("1.13"),
    DEAD_FIRE_CORAL_FAN("1.13"),
    DEAD_FIRE_CORAL_WALL_FAN("1.13"),
    DEAD_HORN_CORAL("1.13"),
    DEAD_HORN_CORAL_BLOCK("1.13"),
    DEAD_HORN_CORAL_FAN("1.13"),
    DEAD_HORN_CORAL_WALL_FAN("1.13"),
    DEAD_TUBE_CORAL("1.13"),
    DEAD_TUBE_CORAL_BLOCK("1.13"),
    DEAD_TUBE_CORAL_FAN("1.13"),
    DEAD_TUBE_CORAL_WALL_FAN("1.13"),
    DEBUG_STICK("1.13", "STICK", ""),
    DETECTOR_RAIL,
    DIAMOND,
    DIAMOND_AXE,
    DIAMOND_BLOCK,
    DIAMOND_BOOTS,
    DIAMOND_CHESTPLATE,
    DIAMOND_HELMET,
    DIAMOND_HOE,
    DIAMOND_HORSE_ARMOR("DIAMOND_BARDING"),
    DIAMOND_LEGGINGS,
    DIAMOND_ORE,
    DIAMOND_PICKAXE,
    DIAMOND_SHOVEL("DIAMOND_SPADE"),
    DIAMOND_SWORD,
    DIORITE(3, "STONE"),
    DIORITE_SLAB,
    DIORITE_STAIRS,
    DIORITE_WALL,
    DIRT,
    DISPENSER,
    DOLPHIN_SPAWN_EGG("1.13", "MONSTER_EGG", ""),
    DONKEY_SPAWN_EGG(32, "MONSTER_EGG"),
    DRAGON_BREATH("DRAGONS_BREATH"),
    DRAGON_EGG,
    DRAGON_HEAD(5, "1.9", "SKULL", "SKULL_ITEM"),
    DRAGON_WALL_HEAD(5, "SKULL", "SKULL_ITEM"),
    DRIED_KELP("1.13"),
    DRIED_KELP_BLOCK("1.13"),
    DROPPER,
    DROWNED_SPAWN_EGG("1.13", "MONSTER_EGG", ""),
    EGG,
    ELDER_GUARDIAN_SPAWN_EGG(4, "MONSTER_EGG"),
    ELYTRA,
    EMERALD,
    EMERALD_BLOCK,
    EMERALD_ORE,
    ENCHANTED_BOOK,
    ENCHANTED_GOLDEN_APPLE(1, "GOLDEN_APPLE"),
    ENCHANTING_TABLE("ENCHANTMENT_TABLE"),
    ENDERMAN_SPAWN_EGG(58, "MONSTER_EGG"),
    ENDERMITE_SPAWN_EGG(67, "MONSTER_EGG"),
    ENDER_CHEST,
    ENDER_EYE("EYE_OF_ENDER"),
    ENDER_PEARL,
    END_CRYSTAL,
    END_GATEWAY("1.9"),
    END_PORTAL("ENDER_PORTAL"),
    END_PORTAL_FRAME("ENDER_PORTAL_FRAME"),
    END_ROD("1.9", "BLAZE_ROD", ""),
    END_STONE("ENDER_STONE"),
    END_STONE_BRICKS("END_BRICKS"),
    END_STONE_BRICK_SLAB(4, "STEP"),
    END_STONE_BRICK_STAIRS("SMOOTH_STAIRS"),
    END_STONE_BRICK_WALL,
    EVOKER_SPAWN_EGG(34, "MONSTER_EGG"),
    EXPERIENCE_BOTTLE("EXP_BOTTLE"),
    FARMLAND("SOIL"),
    FEATHER,
    FERMENTED_SPIDER_EYE,
    FERN(2, "LONG_GRASS"),
    FILLED_MAP("MAP"),
    FIRE,
    FIREWORK_ROCKET("FIREWORK"),
    FIREWORK_STAR("FIREWORK_CHARGE"),
    FIRE_CHARGE("FIREBALL"),
    FIRE_CORAL("1.13"),
    FIRE_CORAL_BLOCK("1.13"),
    FIRE_CORAL_FAN("1.13"),
    FIRE_CORAL_WALL_FAN,
    FISHING_ROD,
    FLETCHING_TABLE("1.14", "CRAFTING_TABLE", ""),
    FLINT,
    FLINT_AND_STEEL,
    FLOWER_BANNER_PATTERN,
    FLOWER_POT("FLOWER_POT_ITEM"),
    FOX_SPAWN_EGG("1.14"),
    /**
     * This special material cannot be obtained as an item.
     */
    FROSTED_ICE("1.9", "PACKED_ICE", ""),
    FURNACE("BURNING_FURNACE"),
    FURNACE_MINECART("POWERED_MINECART"),
    GHAST_SPAWN_EGG(56, "MONSTER_EGG"),
    GHAST_TEAR,
    GLASS,
    GLASS_BOTTLE,
    GLASS_PANE("THIN_GLASS"),
    GLISTERING_MELON_SLICE("SPECKLED_MELON"),
    GLOBE_BANNER_PATTERN,
    GLOWSTONE,
    GLOWSTONE_DUST,
    GOLDEN_APPLE,
    GOLDEN_AXE("GOLD_AXE"),
    GOLDEN_BOOTS("GOLD_BOOTS"),
    GOLDEN_CARROT,
    GOLDEN_CHESTPLATE("GOLD_CHESTPLATE"),
    GOLDEN_HELMET("GOLD_HELMET"),
    GOLDEN_HOE("GOLD_HOE"),
    GOLDEN_HORSE_ARMOR("GOLD_BARDING"),
    GOLDEN_LEGGINGS("GOLD_LEGGINGS"),
    GOLDEN_PICKAXE("GOLD_PICKAXE"),
    GOLDEN_SHOVEL("GOLD_SPADE"),
    GOLDEN_SWORD("GOLD_SWORD"),
    GOLD_BLOCK,
    GOLD_INGOT,
    GOLD_NUGGET,
    GOLD_ORE,
    GRANITE(1, "STONE"),
    GRANITE_SLAB,
    GRANITE_STAIRS,
    GRANITE_WALL,
    GRASS,
    GRASS_BLOCK("GRASS"),
    GRASS_PATH,
    GRAVEL,
    GRAY_BANNER(8, "BANNER", "STANDING_BANNER"),
    GRAY_BED(7, "BED", "BED_BLOCK"),
    GRAY_CARPET(7, "CARPET"),
    GRAY_CONCRETE(7, "CONCRETE"),
    GRAY_CONCRETE_POWDER(7, "CONCRETE_POWDER"),
    GRAY_DYE(8, "INK_SACK"),
    GRAY_GLAZED_TERRACOTTA(7, "1.12", "HARD_CLAY", "STAINED_CLAY", "GRAY_TERRACOTTA"),
    GRAY_SHULKER_BOX,
    GRAY_STAINED_GLASS(7, "STAINED_GLASS"),
    GRAY_STAINED_GLASS_PANE(7, "THIN_GLASS", "STAINED_GLASS_PANE"),
    GRAY_TERRACOTTA(7, "HARD_CLAY", "STAINED_CLAY"),
    GRAY_WALL_BANNER(8, "WALL_BANNER"),
    GRAY_WOOL(7, "WOOL"),
    GREEN_BANNER(2, "BANNER", "STANDING_BANNER"),
    GREEN_BED(13, "BED", "BED_BLOCK"),
    GREEN_CARPET(13, "CARPET"),
    GREEN_CONCRETE(13, "CONCRETE"),
    GREEN_CONCRETE_POWDER(13, "CONCRETE_POWDER"),
    GREEN_DYE(2, "INK_SACK", "CACTUS_GREEN"),
    GREEN_GLAZED_TERRACOTTA(13, "1.12", "HARD_CLAY", "STAINED_CLAY", "GREEN_TERRACOTTA"),
    GREEN_SHULKER_BOX,
    GREEN_STAINED_GLASS(13, "STAINED_GLASS"),
    GREEN_STAINED_GLASS_PANE(13, "THIN_GLASS", "STAINED_GLASS_PANE"),
    GREEN_TERRACOTTA(13, "HARD_CLAY", "STAINED_CLAY"),
    GREEN_WALL_BANNER(2, "WALL_BANNER"),
    GREEN_WOOL(13, "WOOL"),
    GRINDSTONE("1.14", "ANVIL", ""),
    GUARDIAN_SPAWN_EGG(68, "MONSTER_EGG"),
    GUNPOWDER("SULPHUR"),
    HAY_BLOCK,
    HEART_OF_THE_SEA("1.13"),
    HEAVY_WEIGHTED_PRESSURE_PLATE("IRON_PLATE"),
    HONEYCOMB("1.15"),
    HONEYCOMB_BLOCK("1.15"),
    HONEY_BLOCK("1.15", "SLIME_BLOCK", ""),
    HONEY_BOTTLE("1.15", "GLASS_BOTTLE", ""),
    HOPPER,
    HOPPER_MINECART,
    HORN_CORAL("1.13"),
    HORN_CORAL_BLOCK("1.13"),
    HORN_CORAL_FAN("1.13"),
    HORN_CORAL_WALL_FAN,
    HORSE_SPAWN_EGG(100, "MONSTER_EGG"),
    HUSK_SPAWN_EGG(23, "MONSTER_EGG"),
    ICE,
    INFESTED_CHISELED_STONE_BRICKS(5, "MONSTER_EGGS", "SMOOTH_BRICK"),
    INFESTED_COBBLESTONE(1, "MONSTER_EGGS"),
    INFESTED_CRACKED_STONE_BRICKS(4, "MONSTER_EGGS", "SMOOTH_BRICK"),
    INFESTED_MOSSY_STONE_BRICKS(3, "MONSTER_EGGS"),
    INFESTED_STONE("MONSTER_EGGS"),
    INFESTED_STONE_BRICKS(2, "MONSTER_EGGS", "SMOOTH_BRICK"),
    INK_SAC("INK_SACK"),
    IRON_AXE,
    IRON_BARS("IRON_FENCE"),
    IRON_BLOCK,
    IRON_BOOTS,
    IRON_CHESTPLATE,
    IRON_DOOR("IRON_DOOR_BLOCK"),
    IRON_HELMET,
    IRON_HOE,
    IRON_HORSE_ARMOR("IRON_BARDING"),
    IRON_INGOT,
    IRON_LEGGINGS,
    IRON_NUGGET,
    IRON_ORE,
    IRON_PICKAXE,
    IRON_SHOVEL("IRON_SPADE"),
    IRON_SWORD,
    IRON_TRAPDOOR,
    ITEM_FRAME,
    JACK_O_LANTERN,
    JIGSAW("1.14", "COMMAND_BLOCK", "STRUCTURE_BLOCK", ""),
    JUKEBOX,
    JUNGLE_BOAT("BOAT_JUNGLE"),
    JUNGLE_BUTTON("WOOD_BUTTON"),
    JUNGLE_DOOR("JUNGLE_DOOR_ITEM"),
    JUNGLE_FENCE,
    JUNGLE_FENCE_GATE,
    JUNGLE_LEAVES(3, "LEAVES"),
    JUNGLE_LOG(3, "LOG"),
    JUNGLE_PLANKS(3, "WOOD"),
    JUNGLE_PRESSURE_PLATE("WOOD_PLATE"),
    JUNGLE_SAPLING(3, "SAPLING"),
    JUNGLE_SIGN("SIGN"),
    JUNGLE_SLAB(3, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"),
    JUNGLE_STAIRS("JUNGLE_WOOD_STAIRS"),
    JUNGLE_TRAPDOOR("TRAP_DOOR"),
    JUNGLE_WALL_SIGN("SIGN_POST", "WALL_SIGN"),
    JUNGLE_WOOD(3, "LOG"),
    KELP("1.13"),
    KELP_PLANT("1.13"),
    KNOWLEDGE_BOOK("1.12", "BOOK"),
    LADDER,
    LANTERN("1.14", "SEA_LANTERN", ""),
    LAPIS_BLOCK,
    LAPIS_LAZULI(4, "INK_SACK"),
    LAPIS_ORE,
    LARGE_FERN(3, "DOUBLE_PLANT"),
    LAVA("STATIONARY_LAVA"),
    LAVA_BUCKET,
    LEAD("LEASH"),
    LEATHER,
    LEATHER_BOOTS,
    LEATHER_CHESTPLATE,
    LEATHER_HELMET,
    LEATHER_HORSE_ARMOR("1.14", "IRON_HORSE_ARMOR", ""),
    LEATHER_LEGGINGS,
    LECTERN("1.14", "BOOKSHELF", ""),
    LEVER,
    LIGHT_BLUE_BANNER(3, "BANNER", "STANDING_BANNER"),
    LIGHT_BLUE_BED(3, "BED", "BED_BLOCK"),
    LIGHT_BLUE_CARPET(3, "CARPET"),
    LIGHT_BLUE_CONCRETE(3, "CONCRETE"),
    LIGHT_BLUE_CONCRETE_POWDER(3, "CONCRETE_POWDER"),
    LIGHT_BLUE_DYE(12, "INK_SACK"),
    LIGHT_BLUE_GLAZED_TERRACOTTA(3, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIGHT_BLUE_TERRACOTTA"),
    LIGHT_BLUE_SHULKER_BOX,
    LIGHT_BLUE_STAINED_GLASS(3, "STAINED_GLASS"),
    LIGHT_BLUE_STAINED_GLASS_PANE(3, "THIN_GLASS", "STAINED_GLASS_PANE"),
    LIGHT_BLUE_TERRACOTTA(3, "STAINED_CLAY"),
    LIGHT_BLUE_WALL_BANNER(12, "WALL_BANNER", "BANNER", "STANDING_BANNER"),
    LIGHT_BLUE_WOOL(3, "WOOL"),
    LIGHT_GRAY_BANNER(7, "BANNER", "STANDING_BANNER"),
    LIGHT_GRAY_BED(8, "BED", "BED_BLOCK"),
    LIGHT_GRAY_CARPET(8, "CARPET"),
    LIGHT_GRAY_CONCRETE(8, "CONCRETE"),
    LIGHT_GRAY_CONCRETE_POWDER(8, "CONCRETE_POWDER"),
    LIGHT_GRAY_DYE(7, "INK_SACK"),
    /**
     * Renamed to SILVER_GLAZED_TERRACOTTA in 1.13
     * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14
     */
    LIGHT_GRAY_GLAZED_TERRACOTTA(8, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIGHT_GRAY_TERRACOTTA", "SILVER_GLAZED_TERRACOTTA"),
    LIGHT_GRAY_SHULKER_BOX("SILVER_SHULKER_BOX"),
    LIGHT_GRAY_STAINED_GLASS(8, "STAINED_GLASS"),
    LIGHT_GRAY_STAINED_GLASS_PANE(8, "THIN_GLASS", "STAINED_GLASS_PANE"),
    LIGHT_GRAY_TERRACOTTA(8, "HARD_CLAY", "STAINED_CLAY"),
    LIGHT_GRAY_WALL_BANNER(7, "WALL_BANNER"),
    LIGHT_GRAY_WOOL(8, "WOOL"),
    LIGHT_WEIGHTED_PRESSURE_PLATE("GOLD_PLATE"),
    LILAC(1, "DOUBLE_PLANT"),
    LILY_OF_THE_VALLEY(15, "1.14", "WHITE_DYE", ""),
    LILY_PAD("WATER_LILY"),
    LIME_BANNER(10, "BANNER", "STANDING_BANNER"),
    LIME_BED(5, "BED", "BED_BLOCK"),
    LIME_CARPET(5, "CARPET"),
    LIME_CONCRETE(5, "CONCRETE"),
    LIME_CONCRETE_POWDER(5, "CONCRETE_POWDER"),
    LIME_DYE(10, "INK_SACK"),
    LIME_GLAZED_TERRACOTTA(5, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIME_TERRACOTTA"),
    LIME_SHULKER_BOX,
    LIME_STAINED_GLASS(5, "STAINED_GLASS"),
    LIME_STAINED_GLASS_PANE(5, "STAINED_GLASS_PANE"),
    LIME_TERRACOTTA(5, "HARD_CLAY", "STAINED_CLAY"),
    LIME_WALL_BANNER(10, "WALL_BANNER"),
    LIME_WOOL(5, "WOOL"),
    LINGERING_POTION,
    LLAMA_SPAWN_EGG(103, "MONSTER_EGG"),
    LOOM("1.14"),
    MAGENTA_BANNER(13, "BANNER", "STANDING_BANNER"),
    MAGENTA_BED(2, "BED", "BED_BLOCK"),
    MAGENTA_CARPET(2, "CARPET"),
    MAGENTA_CONCRETE(2, "CONCRETE"),
    MAGENTA_CONCRETE_POWDER(2, "CONCRETE_POWDER"),
    MAGENTA_DYE(13, "INK_SACK"),
    MAGENTA_GLAZED_TERRACOTTA(2, "1.12", "HARD_CLAY", "STAINED_CLAY", "MAGENTA_TERRACOTTA"),
    MAGENTA_SHULKER_BOX,
    MAGENTA_STAINED_GLASS(2, "STAINED_GLASS"),
    MAGENTA_STAINED_GLASS_PANE(2, "THIN_GLASS", "STAINED_GLASS_PANE"),
    MAGENTA_TERRACOTTA(2, "HARD_CLAY", "STAINED_CLAY"),
    MAGENTA_WALL_BANNER(13, "WALL_BANNER"),
    MAGENTA_WOOL(2, "WOOL"),
    MAGMA_BLOCK("1.10", "MAGMA"),
    MAGMA_CREAM,
    MAGMA_CUBE_SPAWN_EGG(62, "MONSTER_EGG"),
    MAP("EMPTY_MAP"),
    MELON("MELON_BLOCK"),
    MELON_SEEDS,
    MELON_SLICE("MELON"),
    MELON_STEM,
    MILK_BUCKET,
    MINECART,
    MOJANG_BANNER_PATTERN,
    MOOSHROOM_SPAWN_EGG(96, "MONSTER_EGG"),
    MOSSY_COBBLESTONE,
    MOSSY_COBBLESTONE_SLAB(3, "STEP"),
    MOSSY_COBBLESTONE_STAIRS,
    MOSSY_COBBLESTONE_WALL(1, "COBBLE_WALL", "COBBLESTONE_WALL"),
    MOSSY_STONE_BRICKS(1, "SMOOTH_BRICK"),
    MOSSY_STONE_BRICK_SLAB(4, "STEP"),
    MOSSY_STONE_BRICK_STAIRS("SMOOTH_STAIRS"),
    MOSSY_STONE_BRICK_WALL,
    MOVING_PISTON("PISTON_BASE", "PISTON_MOVING_PIECE"),
    MULE_SPAWN_EGG(32, "MONSTER_EGG"),
    MUSHROOM_STEM("BROWN_MUSHROOM"),
    MUSHROOM_STEW("MUSHROOM_SOUP"),
    MUSIC_DISC_11("GOLD_RECORD"),
    MUSIC_DISC_13("GREEN_RECORD"),
    MUSIC_DISC_BLOCKS("RECORD_3"),
    MUSIC_DISC_CAT("RECORD_4"),
    MUSIC_DISC_CHIRP("RECORD_5"),
    MUSIC_DISC_FAR("RECORD_6"),
    MUSIC_DISC_MALL("RECORD_7"),
    MUSIC_DISC_MELLOHI("RECORD_8"),
    MUSIC_DISC_STAL("RECORD_9"),
    MUSIC_DISC_STRAD("RECORD_10"),
    MUSIC_DISC_WAIT("RECORD_11"),
    MUSIC_DISC_WARD("RECORD_12"),
    MUTTON,
    MYCELIUM("MYCEL"),
    NAME_TAG,
    NAUTILUS_SHELL("1.13"),
    NETHERRACK,
    NETHER_BRICK("NETHER_BRICK_ITEM"),
    NETHER_BRICKS("NETHER_BRICK"),
    NETHER_BRICK_FENCE("NETHER_FENCE"),
    NETHER_BRICK_SLAB(4, "STEP"),
    NETHER_BRICK_STAIRS,
    NETHER_BRICK_WALL,
    NETHER_PORTAL("PORTAL"),
    NETHER_QUARTZ_ORE("QUARTZ_ORE"),
    NETHER_STAR,
    /**
     * Just like mentioned in https://minecraft.gamepedia.com/Nether_Wart
     * Nether wart is also known as nether stalk in the code.
     * NETHER_STALK is the planted state of nether warts.
     */
    NETHER_WART("NETHER_WARTS", "NETHER_STALK"),
    NETHER_WART_BLOCK,
    NOTE_BLOCK,
    OAK_BOAT("BOAT"),
    OAK_BUTTON("WOOD_BUTTON"),
    OAK_DOOR("WOOD_DOOR", "WOODEN_DOOR"),
    OAK_FENCE("FENCE"),
    OAK_FENCE_GATE("FENCE_GATE"),
    OAK_LEAVES("LEAVES"),
    OAK_LOG("LOG"),
    OAK_PLANKS("WOOD"),
    OAK_PRESSURE_PLATE("WOOD_PLATE"),
    OAK_SAPLING("SAPLING"),
    OAK_SIGN("SIGN"),
    OAK_SLAB("WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"),
    OAK_STAIRS("WOOD_STAIRS"),
    OAK_TRAPDOOR("TRAP_DOOR"),
    OAK_WALL_SIGN("SIGN_POST", "WALL_SIGN"),
    OAK_WOOD("LOG"),
    OBSERVER,
    OBSIDIAN,
    OCELOT_SPAWN_EGG(98, "MONSTER_EGG"),
    ORANGE_BANNER(14, "BANNER", "STANDING_BANNER"),
    ORANGE_BED(1, "BED", "BED_BLOCK"),
    ORANGE_CARPET(1, "CARPET"),
    ORANGE_CONCRETE(1, "CONCRETE"),
    ORANGE_CONCRETE_POWDER(1, "CONCRETE_POWDER"),
    ORANGE_DYE(14, "INK_SACK"),
    ORANGE_GLAZED_TERRACOTTA(1, "1.12", "HARD_CLAY", "STAINED_CLAY", "ORANGE_TERRACOTTA"),
    ORANGE_SHULKER_BOX,
    ORANGE_STAINED_GLASS(1, "STAINED_GLASS"),
    ORANGE_STAINED_GLASS_PANE(1, "STAINED_GLASS_PANE"),
    ORANGE_TERRACOTTA(1, "HARD_CLAY", "STAINED_CLAY"),
    ORANGE_TULIP(5, "RED_ROSE"),
    ORANGE_WALL_BANNER(14, "WALL_BANNER"),
    ORANGE_WOOL(1, "WOOL"),
    OXEYE_DAISY(8, "RED_ROSE"),
    PACKED_ICE,
    PAINTING,
    PANDA_SPAWN_EGG("1.14"),
    PAPER,
    PARROT_SPAWN_EGG(105, "MONSTER_EGG"),
    PEONY(5, "DOUBLE_PLANT"),
    PETRIFIED_OAK_SLAB("WOOD_STEP"),
    PHANTOM_MEMBRANE("1.13"),
    PHANTOM_SPAWN_EGG("1.13", "MONSTER_EGG", ""),
    PIG_SPAWN_EGG(90, "MONSTER_EGG"),
    PILLAGER_SPAWN_EGG("1.14"),
    PINK_BANNER(9, "BANNER", "STANDING_BANNER"),
    PINK_BED(6, "BED", "BED_BLOCK"),
    PINK_CARPET(6, "CARPET"),
    PINK_CONCRETE(6, "CONCRETE"),
    PINK_CONCRETE_POWDER(6, "CONCRETE_POWDER"),
    PINK_DYE(9, "INK_SACK"),
    PINK_GLAZED_TERRACOTTA(6, "1.12", "HARD_CLAY", "STAINED_CLAY", "PINK_TERRACOTTA"),
    PINK_SHULKER_BOX,
    PINK_STAINED_GLASS(6, "STAINED_GLASS"),
    PINK_STAINED_GLASS_PANE(6, "THIN_GLASS", "STAINED_GLASS_PANE"),
    PINK_TERRACOTTA(6, "HARD_CLAY", "STAINED_CLAY"),
    PINK_TULIP(7, "RED_ROSE"),
    PINK_WALL_BANNER(14, "WALL_BANNER"),
    PINK_WOOL(6, "WOOL"),
    PISTON("PISTON_BASE"),
    PISTON_HEAD("PISTON_EXTENSION"),
    PLAYER_HEAD(3, "SKULL", "SKULL_ITEM"),
    PLAYER_WALL_HEAD(3, "SKULL", "SKULL_ITEM"),
    PODZOL(2, "DIRT"),
    POISONOUS_POTATO,
    POLAR_BEAR_SPAWN_EGG(102, "MONSTER_EGG"),
    POLISHED_ANDESITE(6, "STONE"),
    POLISHED_ANDESITE_SLAB,
    POLISHED_ANDESITE_STAIRS,
    POLISHED_DIORITE(4, "STONE"),
    POLISHED_DIORITE_SLAB,
    POLISHED_DIORITE_STAIRS,
    POLISHED_GRANITE(2, "STONE"),
    POLISHED_GRANITE_SLAB,
    POLISHED_GRANITE_STAIRS,
    POPPED_CHORUS_FRUIT("CHORUS_FRUIT_POPPED"),
    POPPY("RED_ROSE"),
    PORKCHOP("PORK"),
    POTATO("POTATO_ITEM"),
    POTATOES("POTATO"),
    POTION,
    POTTED_ACACIA_SAPLING(4, "SAPLING", "FLOWER_POT"),
    POTTED_ALLIUM(2, "RED_ROSE", "FLOWER_POT"),
    POTTED_AZURE_BLUET(3, "RED_ROSE", "FLOWER_POT"),
    POTTED_BAMBOO,
    POTTED_BIRCH_SAPLING(2, "SAPLING", "FLOWER_POT"),
    POTTED_BLUE_ORCHID(1, "RED_ROSE", "FLOWER_POT"),
    POTTED_BROWN_MUSHROOM("FLOWER_POT"),
    POTTED_CACTUS("FLOWER_POT"),
    POTTED_CORNFLOWER,
    POTTED_DANDELION("YELLOW_FLOWER", "FLOWER_POT"),
    POTTED_DARK_OAK_SAPLING(5, "SAPLING", "FLOWER_POT"),
    POTTED_DEAD_BUSH("FLOWER_POT"),
    POTTED_FERN(2, "LONG_GRASS", "FLOWER_POT"),
    POTTED_JUNGLE_SAPLING(3, "SAPLING", "FLOWER_POT"),
    POTTED_LILY_OF_THE_VALLEY,
    POTTED_OAK_SAPLING("SAPLING", "FLOWER_POT"),
    POTTED_ORANGE_TULIP(5, "RED_ROSE", "FLOWER_POT"),
    POTTED_OXEYE_DAISY(8, "RED_ROSE", "FLOWER_POT"),
    POTTED_PINK_TULIP(7, "RED_ROSE", "FLOWER_POT"),
    POTTED_POPPY("RED_ROSE", "FLOWER_POT"),
    POTTED_RED_MUSHROOM("FLOWER_POT"),
    POTTED_RED_TULIP(4, "RED_ROSE", "FLOWER_POT"),
    POTTED_SPRUCE_SAPLING(1, "SAPLING", "FLOWER_POT"),
    POTTED_WHITE_TULIP(6, "RED_ROSE", "FLOWER_POT"),
    POTTED_WITHER_ROSE,
    POWERED_RAIL,
    PRISMARINE,
    PRISMARINE_BRICKS(2, "PRISMARINE"),
    PRISMARINE_BRICK_SLAB(4, "STEP"),
    PRISMARINE_BRICK_STAIRS("1.13"),
    PRISMARINE_CRYSTALS,
    PRISMARINE_SHARD,
    PRISMARINE_SLAB("1.13"),
    PRISMARINE_STAIRS("1.13"),
    PRISMARINE_WALL,
    PUFFERFISH(3, "RAW_FISH"),
    PUFFERFISH_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""),
    PUFFERFISH_SPAWN_EGG("1.13", "MONSTER_EGG", ""),
    PUMPKIN,
    PUMPKIN_PIE,
    PUMPKIN_SEEDS,
    PUMPKIN_STEM,
    PURPLE_BANNER(5, "BANNER", "STANDING_BANNER"),
    PURPLE_BED(10, "BED", "BED_BLOCK"),
    PURPLE_CARPET(10, "CARPET"),
    PURPLE_CONCRETE(10, "CONCRETE"),
    PURPLE_CONCRETE_POWDER(10, "CONCRETE_POWDER"),
    PURPLE_DYE(5, "INK_SACK"),
    PURPLE_GLAZED_TERRACOTTA(10, "1.12", "HARD_CLAY", "STAINED_CLAY", "PURPLE_TERRACOTTA"),
    PURPLE_SHULKER_BOX,
    PURPLE_STAINED_GLASS(10, "STAINED_GLASS"),
    PURPLE_STAINED_GLASS_PANE(10, "THIN_GLASS", "STAINED_GLASS_PANE"),
    PURPLE_TERRACOTTA(10, "HARD_CLAY", "STAINED_CLAY"),
    PURPLE_WALL_BANNER(5, "WALL_BANNER"),
    PURPLE_WOOL(10, "WOOL"),
    PURPUR_BLOCK,
    PURPUR_PILLAR,
    PURPUR_SLAB("PURPUR_DOUBLE_SLAB"),
    PURPUR_STAIRS,
    QUARTZ,
    QUARTZ_BLOCK,
    QUARTZ_PILLAR(2, "QUARTZ_BLOCK"),
    QUARTZ_SLAB(7, "STEP"),
    QUARTZ_STAIRS,
    RABBIT,
    RABBIT_FOOT,
    RABBIT_HIDE,
    RABBIT_SPAWN_EGG(101, "MONSTER_EGG"),
    RABBIT_STEW,
    RAIL("RAILS"),
    RAVAGER_SPAWN_EGG("1.14"),
    REDSTONE,
    REDSTONE_BLOCK,
    REDSTONE_LAMP("REDSTONE_LAMP_OFF", "REDSTONE_LAMP_ON"),
    REDSTONE_ORE("GLOWING_REDSTONE_ORE"),
    REDSTONE_TORCH("REDSTONE_TORCH_ON", "REDSTONE_TORCH_OFF"),
    REDSTONE_WALL_TORCH(1, "REDSTONE_TORCH_ON", "REDSTONE_TORCH_OFF"),
    REDSTONE_WIRE,
    RED_BANNER(1, "BANNER", "STANDING_BANNER"),
    RED_BED(14, "BED", "BED_BLOCK"),
    RED_CARPET(14, "CARPET"),
    RED_CONCRETE(14, "CONCRETE"),
    RED_CONCRETE_POWDER(14, "CONCRETE_POWDER"),
    RED_DYE(1, "ROSE_RED"),
    RED_GLAZED_TERRACOTTA(14, "1.12", "HARD_CLAY", "STAINED_CLAY", "RED_TERRACOTTA"),
    RED_MUSHROOM,
    RED_MUSHROOM_BLOCK("RED_MUSHROOM", "HUGE_MUSHROOM_2"),
    RED_NETHER_BRICKS("RED_NETHER_BRICK"),
    RED_NETHER_BRICK_SLAB(4, "STEP"),
    RED_NETHER_BRICK_STAIRS,
    RED_NETHER_BRICK_WALL,
    RED_SAND(1, "SAND"),
    RED_SANDSTONE,
    RED_SANDSTONE_SLAB("STONE_SLAB2", "DOUBLE_STONE_SLAB2"),
    RED_SANDSTONE_STAIRS,
    RED_SANDSTONE_WALL,
    RED_SHULKER_BOX,
    RED_STAINED_GLASS(14, "STAINED_GLASS"),
    RED_STAINED_GLASS_PANE(14, "THIN_GLASS", "STAINED_GLASS_PANE"),
    RED_TERRACOTTA(14, "HARD_CLAY", "STAINED_CLAY"),
    RED_TULIP(4, "RED_ROSE"),
    RED_WALL_BANNER(1, "WALL_BANNER"),
    RED_WOOL(14, "WOOL"),
    REPEATER("DIODE", "DIODE_BLOCK_ON", "DIODE_BLOCK_OFF"),
    REPEATING_COMMAND_BLOCK("COMMAND", "COMMAND_REPEATING"),
    ROSE_BUSH(4, "DOUBLE_PLANT"),
    ROTTEN_FLESH,
    SADDLE,
    SALMON(1, "RAW_FISH"),
    SALMON_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""),
    SALMON_SPAWN_EGG("1.13", "MONSTER_EGG", ""),
    SAND,
    SANDSTONE,
    SANDSTONE_SLAB(1, "STEP", "STONE_SLAB", "DOUBLE_STEP"),
    SANDSTONE_STAIRS,
    SANDSTONE_WALL,
    SCAFFOLDING("1.14", "SLIME_BLOCK", ""),
    SCUTE("1.13"),
    SEAGRASS("1.13", "GRASS", ""),
    SEA_LANTERN,
    SEA_PICKLE("1.13"),
    SHEARS,
    SHEEP_SPAWN_EGG(91, "MONSTER_EGG"),
    SHIELD,
    SHULKER_BOX("PURPLE_SHULKER_BOX"),
    SHULKER_SHELL,
    SHULKER_SPAWN_EGG(69, "MONSTER_EGG"),
    SILVERFISH_SPAWN_EGG(60, "MONSTER_EGG"),
    SKELETON_HORSE_SPAWN_EGG(28, "MONSTER_EGG"),
    SKELETON_SKULL("SKULL", "SKULL_ITEM"),
    SKELETON_SPAWN_EGG(51, "MONSTER_EGG"),
    SKELETON_WALL_SKULL("SKULL", "SKULL_ITEM"),
    SKULL_BANNER_PATTERN,
    SLIME_BALL,
    SLIME_BLOCK,
    SLIME_SPAWN_EGG(55, "MONSTER_EGG"),
    SMITHING_TABLE,
    SMOKER("1.14", "FURNACE", ""),
    SMOOTH_QUARTZ("1.13", "QUARTZ", ""),
    SMOOTH_QUARTZ_SLAB(7, "STEP"),
    SMOOTH_QUARTZ_STAIRS,
    SMOOTH_RED_SANDSTONE(2, "RED_SANDSTONE"),
    SMOOTH_RED_SANDSTONE_SLAB("STONE_SLAB2"),
    SMOOTH_RED_SANDSTONE_STAIRS,
    SMOOTH_SANDSTONE(2, "SANDSTONE"),
    SMOOTH_SANDSTONE_SLAB("STEP"),
    SMOOTH_SANDSTONE_STAIRS,
    SMOOTH_STONE("STEP"),
    SMOOTH_STONE_SLAB("STEP"),
    SNOW,
    SNOWBALL("SNOW_BALL"),
    SNOW_BLOCK,
    SOUL_SAND,
    SPAWNER("MOB_SPAWNER"),
    SPECTRAL_ARROW("1.9", "ARROW", ""),
    SPIDER_EYE,
    SPIDER_SPAWN_EGG(52, "MONSTER_EGG"),
    SPLASH_POTION,
    SPONGE,
    SPRUCE_BOAT("BOAT_SPRUCE"),
    SPRUCE_BUTTON("WOOD_BUTTON"),
    SPRUCE_DOOR("SPRUCE_DOOR_ITEM"),
    SPRUCE_FENCE,
    SPRUCE_FENCE_GATE,
    SPRUCE_LEAVES(1, "LEAVES"),
    SPRUCE_LOG(1, "LOG"),
    SPRUCE_PLANKS(1, "WOOD"),
    SPRUCE_PRESSURE_PLATE("WOOD_PLATE"),
    SPRUCE_SAPLING(1, "SAPLING"),
    SPRUCE_SIGN("SIGN"),
    SPRUCE_SLAB(1, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"),
    SPRUCE_STAIRS("SPRUCE_WOOD_STAIRS"),
    SPRUCE_TRAPDOOR("TRAP_DOOR"),
    SPRUCE_WALL_SIGN("SIGN_POST", "WALL_SIGN"),
    SPRUCE_WOOD(1, "LOG"),
    SQUID_SPAWN_EGG(94, "MONSTER_EGG"),
    STICK,
    STICKY_PISTON("PISTON_BASE", "PISTON_STICKY_BASE"),
    STONE,
    STONECUTTER("1.14"),
    STONE_AXE,
    STONE_BRICKS("SMOOTH_BRICK"),
    STONE_BRICK_SLAB(4, "STEP", "STONE_SLAB", "DOUBLE_STEP"),
    STONE_BRICK_STAIRS("SMOOTH_STAIRS"),
    STONE_BRICK_WALL,
    STONE_BUTTON,
    STONE_HOE,
    STONE_PICKAXE,
    STONE_PRESSURE_PLATE("STONE_PLATE"),
    STONE_SHOVEL("STONE_SPADE"),
    STONE_SLAB("STEP", "DOUBLE_STEP"),
    STONE_STAIRS,
    STONE_SWORD,
    STRAY_SPAWN_EGG(6, "MONSTER_EGG"),
    STRING,
    STRIPPED_ACACIA_LOG("LOG_2"),
    STRIPPED_ACACIA_WOOD("LOG_2"),
    STRIPPED_BIRCH_LOG(2, "LOG"),
    STRIPPED_BIRCH_WOOD(2, "LOG"),
    STRIPPED_DARK_OAK_LOG("LOG"),
    STRIPPED_DARK_OAK_WOOD("LOG"),
    STRIPPED_JUNGLE_LOG(3, "LOG"),
    STRIPPED_JUNGLE_WOOD(3, "LOG"),
    STRIPPED_OAK_LOG("LOG"),
    STRIPPED_OAK_WOOD("LOG"),
    STRIPPED_SPRUCE_LOG(1, "LOG"),
    STRIPPED_SPRUCE_WOOD(1, "LOG"),
    STRUCTURE_BLOCK,
    /**
     * Originally developers used barrier blocks for its purpose.
     * So technically this isn't really considered as a suggested material.
     */
    STRUCTURE_VOID("1.10", "", "BARRIER"),
    SUGAR,
    /**
     * Sugar Cane is a known material in pre-1.13
     * Use XBlock when comparing block types.
     */
    SUGAR_CANE("SUGAR_CANE_BLOCK"),
    SUNFLOWER("DOUBLE_PLANT"),
    SUSPICIOUS_STEW("1.14", "MUSHROOM_STEW", ""),
    SWEET_BERRIES("1.14"),
    SWEET_BERRY_BUSH("1.14", "GRASS", ""),
    TALL_GRASS(2, "DOUBLE_PLANT"),
    TALL_SEAGRASS(2, "1.13", "TALL_GRASS", ""),
    TERRACOTTA("HARD_CLAY"),
    TIPPED_ARROW("1.9", "ARROW", ""),
    TNT,
    TNT_MINECART("EXPLOSIVE_MINECART"),
    TORCH,
    TOTEM_OF_UNDYING("TOTEM"),
    TRADER_LLAMA_SPAWN_EGG(103, "1.14", "MONSTER_EGG", ""),
    TRAPPED_CHEST,
    TRIDENT("1.13"),
    TRIPWIRE,
    TRIPWIRE_HOOK,
    TROPICAL_FISH(2, "RAW_FISH"),
    TROPICAL_FISH_BUCKET("1.13", "BUCKET", "WATER_BUCKET"),
    TROPICAL_FISH_SPAWN_EGG("1.13", "MONSTER_EGG"),
    TUBE_CORAL("1.13"),
    TUBE_CORAL_BLOCK("1.13"),
    TUBE_CORAL_FAN("1.13"),
    TUBE_CORAL_WALL_FAN,
    TURTLE_EGG("1.13", "EGG", ""),
    TURTLE_HELMET("1.13", "IRON_HELMET", ""),
    TURTLE_SPAWN_EGG("1.13", "CHICKEN_SPAWN_EGG", ""),
    VEX_SPAWN_EGG(35, "MONSTER_EGG"),
    VILLAGER_SPAWN_EGG(120, "MONSTER_EGG"),
    VINDICATOR_SPAWN_EGG(36, "MONSTER_EGG"),
    VINE,
    /**
     * 1.13 tag is not added because it's the same thing as {@link #AIR}
     *
     * @see #CAVE_AIR
     */
    VOID_AIR("AIR"),
    WALL_TORCH("TORCH"),
    WANDERING_TRADER_SPAWN_EGG("1.14", "VILLAGER_SPAWN_EGG", ""),
    /**
     * This is used for blocks only.
     * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading.
     * After 1.13+ this uses
     * https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/Levelled.html water flowing system.
     * Use XBlock for this instead.
     */
    WATER("STATIONARY_WATER"),
    WATER_BUCKET,
    WET_SPONGE(1, "SPONGE"),
    /**
     * Wheat is a known material in pre-1.13
     * Use XBlock when comparing block types.
     */
    WHEAT("CROPS"),
    WHEAT_SEEDS("SEEDS"),
    WHITE_BANNER(15, "BANNER", "STANDING_BANNER"),
    WHITE_BED("BED", "BED_BLOCK"),
    WHITE_CARPET("CARPET"),
    WHITE_CONCRETE("CONCRETE"),
    WHITE_CONCRETE_POWDER("CONCRETE_POWDER"),
    WHITE_DYE(15, "1.14", "INK_SACK", "BONE_MEAL"),
    WHITE_GLAZED_TERRACOTTA("1.12", "HARD_CLAY", "STAINED_CLAY", "WHITE_TERRACOTTA"),
    WHITE_SHULKER_BOX,
    WHITE_STAINED_GLASS("STAINED_GLASS"),
    WHITE_STAINED_GLASS_PANE("THIN_GLASS", "STAINED_GLASS_PANE"),
    WHITE_TERRACOTTA("HARD_CLAY", "TERRACOTTA"),
    WHITE_TULIP(6, "RED_ROSE"),
    WHITE_WALL_BANNER(15, "WALL_BANNER"),
    WHITE_WOOL("WOOL"),
    WITCH_SPAWN_EGG(66, "MONSTER_EGG"),
    WITHER_ROSE("1.14", "BLACK_DYE", ""),
    WITHER_SKELETON_SKULL(1, "SKULL", "SKULL_ITEM"),
    WITHER_SKELETON_SPAWN_EGG(5, "MONSTER_EGG"),
    WITHER_SKELETON_WALL_SKULL(1, "SKULL", "SKULL_ITEM"),
    WOLF_SPAWN_EGG(95, "MONSTER_EGG"),
    WOODEN_AXE("WOOD_AXE"),
    WOODEN_HOE("WOOD_HOE"),
    WOODEN_PICKAXE("WOOD_PICKAXE"),
    WOODEN_SHOVEL("WOOD_SPADE"),
    WOODEN_SWORD("WOOD_SWORD"),
    WRITABLE_BOOK("BOOK_AND_QUILL"),
    WRITTEN_BOOK,
    YELLOW_BANNER(11, "BANNER", "STANDING_BANNER"),
    YELLOW_BED(4, "BED", "BED_BLOCK"),
    YELLOW_CARPET(4, "CARPET"),
    YELLOW_CONCRETE(4, "CONCRETE"),
    YELLOW_CONCRETE_POWDER(4, "CONCRETE_POWDER"),
    YELLOW_DYE(11, "INK_SACK", "DANDELION_YELLOW"),
    YELLOW_GLAZED_TERRACOTTA(4, "1.12", "HARD_CLAY", "STAINED_CLAY", "YELLOW_TERRACOTTA"),
    YELLOW_SHULKER_BOX,
    YELLOW_STAINED_GLASS(4, "STAINED_GLASS"),
    YELLOW_STAINED_GLASS_PANE(4, "THIN_GLASS", "STAINED_GLASS_PANE"),
    YELLOW_TERRACOTTA(4, "HARD_CLAY", "STAINED_CLAY"),
    YELLOW_WALL_BANNER(11, "WALL_BANNER"),
    YELLOW_WOOL(4, "WOOL"),
    ZOMBIE_HEAD(2, "SKULL", "SKULL_ITEM"),
    ZOMBIE_HORSE_SPAWN_EGG(29, "MONSTER_EGG"),
    ZOMBIE_PIGMAN_SPAWN_EGG(57, "MONSTER_EGG"),
    ZOMBIE_SPAWN_EGG(54, "MONSTER_EGG"),
    ZOMBIE_VILLAGER_SPAWN_EGG(27, "MONSTER_EGG"),
    ZOMBIE_WALL_HEAD(2, "SKULL", "SKULL_ITEM");


    /**
     * An immutable cached set of {@link XMaterial#values()} to avoid allocating memory for
     * calling the method every time.
     *
     * @since 2.0.0
     */
    public static final EnumSet<XMaterial> VALUES = EnumSet.allOf(XMaterial.class);
    /**
     * A set of material names that can be damaged.
     * <p>
     * Most of the names are not complete as this list is intended to be
     * checked with {@link String#contains} for memory usage.
     *
     * @since 1.0.0
     */
    private static final ImmutableSet<String> DAMAGEABLE = ImmutableSet.of(
            "HELMET", "CHESTPLATE", "LEGGINGS", "BOOTS",
            "SWORD", "AXE", "PICKAXE", "SHOVEL", "HOE",
            "ELYTRA", "TRIDENT", "HORSE_ARMOR", "BARDING",
            "SHEARS", "FLINT_AND_STEEL", "BOW", "FISHING_ROD",
            "CARROT_ON_A_STICK", "CARROT_STICK", "SPADE", "SHIELD"
    );
    /**
     * <b>XMaterial Paradox (Duplication Check)</b>
     * <p>
     * A map of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names.
     * Values are the new material names.
     * <br>
     * Duplicates are normally only checked by keys, not values.
     *
     * @since 3.0.0
     */
    @SuppressWarnings("UnstableApiUsage")
    private static final ImmutableMap<XMaterial, XMaterial> DUPLICATED = Maps.immutableEnumMap(ImmutableMap.<XMaterial, XMaterial>builder()
            .put(MELON, MELON_SLICE)
            .put(CARROT, CARROTS)
            .put(POTATO, POTATOES)
            .put(BEETROOT, BEETROOTS)
            .put(BROWN_MUSHROOM, BROWN_MUSHROOM_BLOCK)
            .put(BRICK, BRICKS)
            .put(RED_MUSHROOM, RED_MUSHROOM_BLOCK)
            .put(MAP, FILLED_MAP)
            .put(NETHER_BRICK, NETHER_BRICKS)
            .build()
    );
    /*
     * A set of all the legacy names without duplicates.
     * <p>
     * It'll help to free up a lot of memory if it's not used.
     * Add it back if you need it.
     *
     * @see #containsLegacy(String)
     * @since 2.2.0
     *
    private static final ImmutableSet<String> LEGACY_VALUES = VALUES.stream().map(XMaterial::getLegacy)
            .flatMap(Arrays::stream)
            .filter(m -> m.charAt(1) == '.')
            .collect(Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf));
    */

    /**
     * Guava (Google Core Libraries for Java)'s cache for performance and timed caches.
     * For strings that match a certain XMaterial. Mostly cached for configs.
     *
     * @since 1.0.0
     */
    private static final Cache<String, XMaterial> NAME_CACHE = CacheBuilder.newBuilder()
            .softValues()
            .expireAfterAccess(15, TimeUnit.MINUTES)
            .build();
    /**
     * Guava (Google Core Libraries for Java)'s cache for performance and timed caches.
     * For XMaterials that are already parsed once.
     *
     * @since 3.0.0
     */
    private static final Cache<XMaterial, Optional<Material>> PARSED_CACHE = CacheBuilder.newBuilder()
            .softValues()
            .expireAfterAccess(10, TimeUnit.MINUTES)
            .concurrencyLevel(Runtime.getRuntime().availableProcessors())
            .build();

    /**
     * Pre-compiled RegEx pattern.
     * Include both replacements to avoid recreating string multiple times with multiple RegEx checks.
     *
     * @since 3.0.0
     */
    private static final Pattern FORMAT_PATTERN = Pattern.compile("\\W+");
    /**
     * The current version of the server in the a form of a major version.
     *
     * @since 1.0.0
     */
    private static final int VERSION = Integer.parseInt(getMajorVersion(Bukkit.getVersion()).substring(2));
    /**
     * Cached result if the server version is after the v1.13 flattening update.
     * Please don't mistake this with flat-chested people. It happened.
     *
     * @since 3.0.0
     */
    private static final boolean ISFLAT = supports(13);
    /**
     * The data value of this material https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening
     *
     * @see #getData()
     */
    private final byte data;
    /**
     * A list of material names that was being used for older verions.
     *
     * @see #getLegacy()
     */
    private final String[] legacy;

    XMaterial(int data, String... legacy) {
        this.data = (byte) data;
        this.legacy = legacy;
    }

    XMaterial() {
        this(0);
    }

    XMaterial(String... legacy) {
        this(0, legacy);
    }

    /**
     * Checks if the version is 1.13 Aquatic Update or higher.
     * An invocation of this method yields the cached result from the expression:
     * <p>
     * <blockquote>
     * {@link #supports(int) 13}}
     * </blockquote>
     *
     * @return true if 1.13 or higher.
     * @see #getVersion()
     * @see #supports(int)
     * @since 1.0.0
     */
    public static boolean isNewVersion() {
        return ISFLAT;
    }

    /**
     * This is just an extra method that method that can be used for many cases.
     * It can be used in {@link org.bukkit.event.player.PlayerInteractEvent}
     * or when accessing {@link org.bukkit.entity.Player#getMainHand()},
     * or other compatibility related methods.
     * <p>
     * An invocation of this method yields exactly the same result as the expression:
     * <p>
     * <blockquote>
     * {@link #getVersion()} == 1.8
     * </blockquote>
     *
     * @since 2.0.0
     */
    public static boolean isOneEight() {
        return !supports(9);
    }

    /**
     * The current version of the server.
     *
     * @return the current server version or 0.0 if unknown.
     * @see #isNewVersion()
     * @since 2.0.0
     */
    public static double getVersion() {
        return VERSION;
    }

    /**
     * When using newer versions of Minecraft ({@link #isNewVersion()}), helps
     * to find the old material name with its data value using a cached search for optimization.
     *
     * @see #matchDefinedXMaterial(String, byte)
     * @since 1.0.0
     */
    @Nullable
    private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) {
        String holder = name + data;
        XMaterial material = NAME_CACHE.getIfPresent(holder);
        if (material != null) return material;

        for (XMaterial materials : VALUES) {
            if ((data == -1 || data == materials.data) && materials.anyMatchLegacy(name)) {
                NAME_CACHE.put(holder, materials);
                return materials;
            }
        }

        return null;
    }

    /**
     * Checks if XMaterial enum contains a material with the given name.
     * <p>
     * You should use {@link #matchXMaterial(String)} instead if you're going
     * to get the XMaterial object after checking if it's available in the list
     * by doing a simple {@link Optional#isPresent()} check.
     * This is just to avoid multiple loops for maximum performance.
     *
     * @param name name of the material.
     * @return true if XMaterial enum has this material.
     * @since 1.0.0
     */
    public static boolean contains(@Nonnull String name) {
        Validate.notEmpty(name, "Cannot check for null or empty material name");
        name = format(name);

        for (XMaterial materials : VALUES)
            if (materials.name().equals(name)) return true;
        return false;
    }

    /**
     * Parses the given material name as an XMaterial with unspecified data value.
     *
     * @see #matchXMaterial(String, byte)
     * @since 2.0.0
     */
    @Nonnull
    public static Optional<XMaterial> matchXMaterial(@Nonnull String name) {
        return matchXMaterial(name, (byte) -1);
    }

    /**
     * Parses the given material name as an XMaterial.
     * Can also be used like: <b>MATERIAL:DATA</b>
     * <p>
     * <b>Examples</b>
     * <pre>
     *     {@code INK_SACK:1 -> RED_DYE}
     *     {@code WOOL, 14  -> RED_WOOL}
     * </pre>
     *
     * @see #matchDefinedXMaterial(String, byte)
     * @see #matchXMaterial(ItemStack)
     * @since 2.0.0
     */
    @Nonnull
    public static Optional<XMaterial> matchXMaterial(@Nonnull String name, byte data) {
        Validate.notEmpty(name, "Cannot match a material with null or empty material name");
        Optional<XMaterial> oldMatch = matchXMaterialWithData(name);
        if (oldMatch.isPresent()) return oldMatch;

        // -1 Determines whether the item's data value is unknown and only the name is given.
        // Checking if the item is damageable won't do anything as the data is not going to be checked in requestOldMaterial anyway.
        return matchDefinedXMaterial(format(name), data);
    }

    /**
     * Parses material name and data value from the specified string.
     * The seperators are: <b>, or :</b>
     * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters.
     * <p>
     * <b>Examples</b>
     * <p><pre>
     *     {@code INK_SACK:1 -> RED_DYE}
     *     {@code WOOL, 14  -> RED_WOOL}
     * </pre>
     *
     * @param name the material string that consists of the material name, data and separator character.
     * @return the parsed XMaterial.
     * @see #matchXMaterial(String)
     * @since 3.0.0
     */
    private static Optional<XMaterial> matchXMaterialWithData(String name) {
        for (char separator : new char[]{',', ':'}) {
            int index = name.indexOf(separator);
            if (index == -1) continue;

            String mat = format(name.substring(0, index));
            byte data = Byte.parseByte(StringUtils.deleteWhitespace(name.substring(index + 1)));
            return matchDefinedXMaterial(mat, data);
        }

        return Optional.empty();
    }

    /**
     * Parses the given material as an XMaterial.
     *
     * @throws IllegalArgumentException may be thrown as an unexpected exception.
     * @see #matchDefinedXMaterial(String, byte)
     * @see #matchXMaterial(ItemStack)
     * @since 2.0.0
     */
    @Nonnull
    public static XMaterial matchXMaterial(@Nonnull Material material) {
        Objects.requireNonNull(material, "Cannot match null material");
        return matchDefinedXMaterial(material.name(), (byte) -1)
                .orElseThrow(() -> new IllegalArgumentException("Unsupported Material: " + material));
    }

    /**
     * Parses the given item as an XMaterial using its material and data value (durability).
     *
     * @param item the ItemStack to match.
     * @return an XMaterial if matched any.
     * @throws IllegalArgumentException may be thrown as an unexpected exception.
     * @see #matchDefinedXMaterial(String, byte)
     * @since 2.0.0
     */
    @Nonnull
    @SuppressWarnings("deprecation")
    public static XMaterial matchXMaterial(@Nonnull ItemStack item) {
        Objects.requireNonNull(item, "Cannot match null ItemStack");
        String material = item.getType().name();
        return matchDefinedXMaterial(material,
                isDamageable(material) ? (byte) 0 : (byte) item.getDurability())
                .orElseThrow(() -> new IllegalArgumentException("Unsupported Material: " + material));
    }

    /**
     * Parses the given material name and data value as an XMaterial.
     * All the values passed to this method will not be null or empty and are formatted correctly.
     *
     * @param name the formatted name of the material.
     * @param data the data value of the material.
     * @return an XMaterial (with the same data value if specified)
     * @see #matchXMaterial(String, byte)
     * @see #matchXMaterial(Material)
     * @see #matchXMaterial(int, byte)
     * @see #matchXMaterial(ItemStack)
     * @since 3.0.0
     */
    @Nonnull
    private static Optional<XMaterial> matchDefinedXMaterial(@Nonnull String name, byte data) {
        boolean duplicated = isDuplicated(name);

        // Do basic number and boolean checks before accessing more complex enum stuff.
        // Maybe we can simplify (ISFLAT || !duplicated) with the (!ISFLAT && duplicated) under it to save a few nanoseconds?
        // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null;
        if (data <= 0 && (ISFLAT || !duplicated)) {
            // Apparently the transform method is more efficient than toJavaUtil()
            // toJavaUtil isn't even supported in older versions.
            Optional<XMaterial> xMat = Enums.getIfPresent(XMaterial.class, name).transform(Optional::of).or(Optional.empty());
            if (xMat.isPresent()) return xMat;
        }

        // XMaterial Paradox (Duplication Check)
        // I've concluded that this is just an infinite loop that keeps
        // going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time.
        // This solution works just fine anyway.
        if (!ISFLAT && duplicated) return Optional.ofNullable(requestDuplicatedXMaterial(name, data));
        return Optional.ofNullable(requestOldXMaterial(name, data));
    }

    /**
     * <b>XMaterial Paradox (Duplication Check)</b>
     * Checks if the material has any duplicates.
     * <p>
     * <b>Example:</b>
     * <p>{@code MELON, CARROT, POTATO, BEETROOT -> true}
     *
     * @param name the name of the material to check.
     * @return true if there's a duplicated material for this material, otherwise false.
     * @see #isDuplicated()
     * @since 2.0.0
     */
    public static boolean isDuplicated(@Nonnull String name) {
        Validate.notEmpty(name, "Cannot check duplication for null or empty material name");
        name = format(name);

        // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError.
        for (Map.Entry<XMaterial, XMaterial> duplicated : DUPLICATED.entrySet())
            if (duplicated.getKey().name().equals(name) || duplicated.getKey().anyMatchLegacy(name)) return true;
        return false;
    }

    /**
     * Gets the XMaterial based on the material's ID (Magic Value) and data value.<br>
     * You should avoid using this for performance issues.
     *
     * @param id   the ID (Magic value) of the material.
     * @param data the data value of the material.
     * @return a parsed XMaterial with the same ID and data value.
     * @see #matchXMaterial(ItemStack)
     * @since 2.0.0
     */
    @Nonnull
    public static Optional<XMaterial> matchXMaterial(int id, byte data) {
        if (id < 0 || data < 0) return Optional.empty();

        // Looping through Material.values() will take longer.
        for (XMaterial materials : VALUES)
            if (materials.data == data && materials.getId() == id) return Optional.of(materials);
        return Optional.empty();
    }

    /**
     * A solution for <b>XMaterial Paradox</b>.
     * Manually parses the duplicated materials to find the exact material based on the server version.
     *
     * @param name the name of the material.
     * @return the duplicated XMaterial based on the version.
     * @throws IllegalArgumentException may be thrown. If thrown, it's a bug.
     * @since 2.0.0
     */
    @Nullable
    private static XMaterial requestDuplicatedXMaterial(@Nonnull String name, byte data) {
        XMaterial mat = requestOldXMaterial(name, data);
        // If ends with "S" -> Plural Form Material
        return mat.name().charAt(mat.name().length() - 1) == 'S' ? Enums.getIfPresent(XMaterial.class, name).orNull() : mat;
    }

    /**
     * Always returns the value with the given duplicated material key name.
     *
     * @param name the name of the material.
     * @return the new XMaterial of this duplicated material.
     * @see #getXMaterialIfDuplicated(String)
     * @since 2.0.0
     */
    @Nonnull
    public static Optional<XMaterial> getNewXMaterialIfDuplicated(@Nonnull String name) {
        Validate.notEmpty(name, "Cannot get new duplicated material for null or empty material name");
        name = format(name);

        for (Map.Entry<XMaterial, XMaterial> duplicated : DUPLICATED.entrySet())
            if (duplicated.getKey().name().equals(name)) return Optional.of(duplicated.getKey());
        return Optional.empty();
    }

    /**
     * Checks if the item is duplicated for a different purpose in new versions from {@link #DUPLICATED}.
     *
     * @param name the name of the material.
     * @return the other XMaterial (key or value) of the XMaterial (key or value).
     * @see #matchXMaterial(String, byte)
     * @since 2.0.0
     */
    @Nullable
    public static XMaterial getXMaterialIfDuplicated(@Nonnull String name) {
        Validate.notEmpty(name, "Cannot get duplicated material for null or empty material name");
        name = format(name);

        for (Map.Entry<XMaterial, XMaterial> duplicated : DUPLICATED.entrySet())
            if (duplicated.getKey().name().equals(name)) return duplicated.getValue();
            else if (duplicated.getValue().name().equals(name)) return duplicated.getKey();

        return null;
    }

    /**
     * Attempts to build the string like an enum name.
     * Removes all the spaces, numbers and extra non-English characters. Also removes some config/in-game based strings.
     *
     * @param name the material name to modify.
     * @return a Material enum name.
     * @since 2.0.0
     */
    @Nonnull
    private static String format(@Nonnull String name) {
        return FORMAT_PATTERN.matcher(
                name.trim().replace('-', '_').replace(' ', '_')).replaceAll("").toUpperCase(Locale.ENGLISH);
    }

    /**
     * Checks if the specified version is the same version or higher than the current server version.
     *
     * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9
     * @return true of the version is equal or higher than the current version.
     * @since 2.0.0
     */
    public static boolean supports(int version) {
        return VERSION >= version;
    }

    /**
     * Converts the enum names to a more friendly and readable string.
     *
     * @return a formatted string.
     * @see #toWord(String)
     * @since 2.1.0
     */
    @Nonnull
    public static String toWord(@Nonnull Material material) {
        Objects.requireNonNull(material, "Cannot translate a null material to a word");
        return toWord(material.name());
    }

    /**
     * Parses an enum name to a normal word.
     * Normal names have underlines removed and each word capitalized.
     * <p>
     * <b>Examples:</b>
     * <pre>
     *     EMERALD                 -> Emerald
     *     EMERALD_BLOCK           -> Emerald Block
     *     ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple
     * </pre>
     *
     * @param name the name of the enum.
     * @return a cleaned more readable enum name.
     * @since 2.1.0
     */
    @Nonnull
    private static String toWord(@Nonnull String name) {
        return WordUtils.capitalize(name.replace('_', ' ').toLowerCase(Locale.ENGLISH));
    }

    /**
     * Gets the exact major version (..., 1.9, 1.10, ..., 1.14)
     *
     * @param version Supports {@link Bukkit#getVersion()}, {@link Bukkit#getBukkitVersion()} and normal formats such as "1.14"
     * @return the exact major version.
     * @since 2.0.0
     */
    @Nonnull
    public static String getMajorVersion(@Nonnull String version) {
        Validate.notEmpty(version, "Cannot get major Minecraft version from null or empty string");

        // getVersion()
        int index = version.lastIndexOf("MC:");
        if (index != -1) {
            version = version.substring(index + 4, version.length() - 1);
        } else if (version.endsWith("SNAPSHOT")) {
            // getBukkitVersion()
            index = version.indexOf('-');
            version = version.substring(0, index);
        }

        // 1.13.2, 1.14.4, etc...
        int lastDot = version.lastIndexOf('.');
        if (version.indexOf('.') != lastDot) version = version.substring(0, lastDot);

        return version;
    }

    /**
     * Checks if the material can be damaged by using it.
     * Names going through this method are not formatted.
     *
     * @param name the name of the material.
     * @return true of the material can be damaged.
     * @see #isDamageable()
     * @since 1.0.0
     */
    public static boolean isDamageable(@Nonnull String name) {
        Objects.requireNonNull(name, "Material name cannot be null");
        for (String damageable : DAMAGEABLE)
            if (name.contains(damageable)) return true;
        return false;
    }

    /**
     * Checks if the list of given material names matches the given base material.
     * Mostly used for configs.
     * <p>
     * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats.
     * <p>
     * <b>Example:</b>
     * <blockquote><pre>
     *     XMaterial material = {@link #matchXMaterial(ItemStack)};
     *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
     * </pre></blockquote>
     * <br>
     * <b>{@code CONTAINS} Examples:</b>
     * <pre>
     *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
     *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
     * </pre>
     * <p>
     * <b>{@code REGEX} Examples</b>
     * <pre>
     *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
     *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
     * </pre>
     * <p>
     * The reason that there are tags for {@code CONTAINS} and {@code REGEX}
     * is for the performance.
     * Please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag.
     * It'll have a huge impact on performance.
     * Please avoid using {@code (capturing groups)} there's no use for them in this case.
     * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster.
     * <p>
     * You can make a cache for pre-compiled RegEx patterns from your config.
     * It's better, but not much faster since these patterns are not that complex.
     * <p>
     * Want to learn RegEx? You can mess around in <a href="https://regexr.com/">RegExr</a> website.
     *
     * @param material  the base material to match other materials with.
     * @param materials the material names to check base material on.
     * @return true if one of the given material names is similar to the base material.
     * @since 3.1.1
     */
    public static boolean isOneOf(@Nonnull Material material, @Nullable List<String> materials) {
        if (materials == null || materials.isEmpty()) return false;
        Objects.requireNonNull(material, "Cannot match materials with a null material");
        String name = material.name();

        for (String comp : materials) {
            comp = comp.toUpperCase();
            if (comp.startsWith("CONTAINS:")) {
                comp = format(comp.substring(9));
                if (name.contains(comp)) return true;
                continue;
            }
            if (comp.startsWith("REGEX:")) {
                comp = comp.substring(6);
                if (name.matches(comp)) return true;
                continue;
            }

            // Direct Object Equals
            Optional<XMaterial> mat = matchXMaterial(comp);
            if (mat.isPresent() && mat.get().parseMaterial() == material) return true;
        }
        return false;
    }

    /**
     * Gets the version which this material was added in.
     * If the material doesn't have a version it'll return 0;
     *
     * @return the Minecraft version which tihs material was added in.
     * @since 3.0.0
     */
    public int getMaterialVersion() {
        if (this.legacy.length == 0) return 0;
        String version = this.legacy[0];
        if (version.charAt(1) != '.') return 0;

        return Integer.parseInt(version.substring(2));
    }

    /**
     * Sets the {@link Material} (and data value on older versions) of an item.
     * Damageable materials will not have their durability changed.
     * <p>
     * Use {@link #parseItem()} instead when creating new ItemStacks.
     *
     * @param item the item to change its type.
     * @see #parseItem()
     * @since 3.0.0
     */
    @Nonnull
    @SuppressWarnings("deprecation")
    public ItemStack setType(@Nonnull ItemStack item) {
        Objects.requireNonNull(item, "Cannot set material for null ItemStack");

        item.setType(this.parseMaterial());
        if (!ISFLAT && !this.isDamageable()) item.setDurability(this.data);
        return item;
    }

    /**
     * Checks if the list of given material names matches the given base material.
     * Mostly used for configs.
     *
     * @param materials the material names to check base material on.
     * @return true if one of the given material names is similar to the base material.
     * @see #isOneOf(Material, List)
     * @since 3.0.0
     */
    public boolean isOneOf(@Nullable List<String> materials) {
        Material material = this.parseMaterial();
        if (material == null) return false;
        return isOneOf(material, materials);
    }

    /**
     * Checks if the given string matches any of this material's legacy material names.
     * All the values passed to this method will not be null or empty and are formatted correctly.
     *
     * @param name the name to check
     * @return true if it's one of the legacy names.
     * @since 2.0.0
     */
    private boolean anyMatchLegacy(@Nonnull String name) {
        for (String legacy : this.legacy) {
            if (legacy.isEmpty()) break; // Left-side suggestion list
            if (name.equals(legacy)) return true;
        }
        return false;
    }

    /**
     * User-friendly readable name for this material
     * In most cases you should be using {@link #name()} instead.
     *
     * @return string of this object.
     * @see #toWord(String)
     * @since 3.0.0
     */
    @Override
    public String toString() {
        return toWord(this.name());
    }

    /**
     * Gets the ID (Magic value) of the material.
     *
     * @return the ID of the material or <b>-1</b> if it's a new block or the material is not supported.
     * @see #matchXMaterial(int, byte)
     * @since 2.2.0
     */
    @SuppressWarnings("deprecation")
    public int getId() {
        if (this.data != 0 || (this.legacy.length != 0 && Integer.parseInt(this.legacy[0].substring(2)) >= 13)) return -1;
        Material material = this.parseMaterial();
        return material == null ? -1 : material.getId();
    }

    /**
     * Checks if the material has any duplicates.
     *
     * @return true if there is a duplicated name for this material, otherwise false.
     * @see #getXMaterialIfDuplicated()
     * @see #isDuplicated(String)
     * @since 2.0.0
     */
    public boolean isDuplicated() {
        return DUPLICATED.containsKey(this);
    }

    /**
     * Checks if the item is duplicated for a different purpose in new versions.
     *
     * @return true if the item's name is duplicated, otherwise false.
     * @see #isDuplicated()
     * @see #getNewXMaterialIfDuplicated(String)
     * @since 2.0.0
     */
    @Nullable
    public XMaterial getXMaterialIfDuplicated() {
        return DUPLICATED.get(this);
    }

    /**
     * Checks if the material can be damaged by using it.
     * Names going through this method are not formatted.
     *
     * @return true if the item can be damaged (have its durability changed), otherwise false.
     * @see #isDamageable(String)
     * @since 1.0.0
     */
    public boolean isDamageable() {
        return isDamageable(this.name());
    }

    /**
     * The data value of this material <a href="https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening">pre-flattening</a>.
     * <p>
     * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()}
     * or {@link ItemStack#getDurability()} if not damageable.
     *
     * @return data of this material, or 0 if none.
     * @since 1.0.0
     */
    @SuppressWarnings("deprecation")
    public byte getData() {
        return data;
    }

    /**
     * Get a list of materials names that was previously used by older versions.
     * If the material was added in a new version {@link #isNewVersion()},
     * then the first element will indicate which version the material was added in.
     *
     * @return a list of legacy material names and the first element as the version the material was added in if new.
     * @since 1.0.0
     */
    @Nonnull
    public String[] getLegacy() {
        return legacy;
    }

    /**
     * Parses an item from this XMaterial.
     * Uses data values on older versions.
     *
     * @return an ItemStack with the same material (and data value if in older versions.)
     * @see #parseItem(boolean)
     * @see #setType(ItemStack)
     * @since 1.0.0
     */
    @Nullable
    public ItemStack parseItem() {
        return parseItem(false);
    }

    /**
     * Parses an item from this XMaterial.
     * Uses data values on older versions.
     *
     * @param suggest if true {@link #parseMaterial(boolean)} true will be used.
     * @return an ItemStack with the same material (and data value if in older versions.)
     * @see #setType(ItemStack)
     * @since 2.0.0
     */
    @Nullable
    @SuppressWarnings("deprecation")
    public ItemStack parseItem(boolean suggest) {
        Material material = this.parseMaterial(suggest);
        if (material == null) return null;
        return ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data);
    }

    /**
     * Parses the material of this XMaterial.
     *
     * @return the material related to this XMaterial based on the server version.
     * @see #parseMaterial(boolean)
     * @since 1.0.0
     */
    @Nullable
    public Material parseMaterial() {
        return parseMaterial(false);
    }

    /**
     * Parses the material of this XMaterial and accepts suggestions.
     *
     * @param suggest use a suggested material (from older materials) if the material is added in a later version of Minecraft.
     * @return the material related to this XMaterial based on the server version.
     * @see #matchXMaterial(String, byte)
     * @since 2.0.0
     */
    @SuppressWarnings("OptionalAssignedToNull")
    @Nullable
    public Material parseMaterial(boolean suggest) {
        Optional<Material> cache = PARSED_CACHE.getIfPresent(this);
        if (cache != null) return cache.orElse(null);
        Material mat;

        if (!ISFLAT && this.isDuplicated()) mat = requestOldMaterial(suggest);
        else {
            mat = Material.getMaterial(this.name());
            if (mat == null) mat = requestOldMaterial(suggest);
        }

        if (mat != null) PARSED_CACHE.put(this, Optional.ofNullable(mat));
        return mat;
    }

    /**
     * Parses a material for older versions of Minecraft.
     * Accepts suggestions if specified.
     *
     * @param suggest if true suggested materials will be considered for old versions.
     * @return a parsed material suitable for the current Minecraft version.
     * @see #parseMaterial(boolean)
     * @since 2.0.0
     */
    @Nullable
    private Material requestOldMaterial(boolean suggest) {
        for (int i = this.legacy.length - 1; i >= 0; i--) {
            String legacy = this.legacy[i];

            // Check if we've reached the end and the last string is our
            // material version.
            if (i == 0 && legacy.charAt(1) == '.') return null;

            // According to the suggestion list format, all the other names continuing
            // from here are considered as a "suggestion"
            // The empty string is an indicator for suggestion list on the left side.
            if (legacy.isEmpty()) {
                if (suggest) continue;
                break;
            }

            Material material = Material.getMaterial(legacy);
            if (material != null) return material;
        }
        return null;
    }

    /**
     * Checks if an item has the same material (and data value on older versions).
     *
     * @param item item to check.
     * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false.
     * @since 1.0.0
     */
    @SuppressWarnings("deprecation")
    public boolean isSimilar(@Nonnull ItemStack item) {
        Objects.requireNonNull(item, "Cannot compare with null ItemStack");
        if (item.getType() != this.parseMaterial()) return false;
        return ISFLAT || this.isDamageable() || item.getDurability() == this.data;
    }

    /**
     * Gets the suggested material names that can be used
     * if the material is not supported in the current version.
     *
     * @return a list of suggested material names.
     * @see #parseMaterial(boolean)
     * @since 2.0.0
     */
    @Nonnull
    public List<String> getSuggestions() {
        if (this.legacy.length == 0 || this.legacy[0].charAt(1) != '.') return new ArrayList<>();
        List<String> suggestions = new ArrayList<>();
        for (String legacy : this.legacy) {
            if (legacy.isEmpty()) break;
            suggestions.add(legacy);
        }
        return suggestions;
    }

    /**
     * Checks if this material is supported in the current version.
     * Suggested materials will be ignored.
     * <p>
     * Note that you should use {@link #parseMaterial()} and check if it's null
     * if you're going to parse and use the material later.
     *
     * @return true if the material exists in {@link Material} list.
     * @since 2.0.0
     */
    public boolean isSupported() {
        int version = this.getMaterialVersion();
        if (version != 0) return supports(version);

        Material material = Material.getMaterial(this.name());
        if (material != null) return true;
        return requestOldMaterial(false) != null;
    }

    /**
     * Checks if the material is newly added after the 1.13 Aquatic Update.
     *
     * @return true if the material was newly added, otherwise false.
     * @see #getMaterialVersion()
     * @since 2.0.0
     */
    public boolean isFromNewSystem() {
        return this.legacy.length != 0 && Integer.parseInt(this.legacy[0].substring(2)) > 13;
    }
}
